//--------------------------------------------------------------------------
// $Id: SignatureVerifier.java 12 2006-08-28 05:05:13Z zendragon $
//--------------------------------------------------------------------------
// Copyright 2006 Social Brain Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//--------------------------------------------------------------------------

package org.socialbrain.openid.consumer;

import org.socialbrain.openid.Assertion;
import org.socialbrain.openid.CheckAuthRequest;
import org.socialbrain.openid.CheckAuthResponse;
import org.socialbrain.openid.Constants;
import org.socialbrain.openid.KeyValuePairs;
import org.socialbrain.openid.OpenIdException;
import org.socialbrain.openid.Utils;
import org.socialbrain.openid.security.Signatory;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.List;

/**
 * @author <a href="http://erning.net/">Zhang Erning</a>
 * @version $Id: SignatureVerifier.java 12 2006-08-28 05:05:13Z zendragon $
 */
public class SignatureVerifier implements Verifier
{
    public boolean verify(String identity, Assertion assertion)
    {
        AssociationSession session = _associationSessionManager.lookupByIdentity(identity);
        if (session == null)
        {
            Discovered discovered = null;
            for (Discoverer discoverer : _discoverers)
            {
                discovered = discoverer.discover(identity);
                if (discovered != null) break;
            }
            return discovered != null && checkAuth(discovered.getEndpoint(), assertion);
        }

        Signatory signatory = findSignatory(session.getAssocType());
        if (signatory != null)
        {
            return checkSig(signatory, session, assertion);
        }
        else
        {
            return checkAuth(session.getEndpoint(), assertion);
        }
    }

    protected Signatory findSignatory(String assocType)
    {
        for (Signatory signatory : _signatories)
        {
            if (signatory.isAcceptAssocType(assocType))
            {
                return signatory;
            }
        }
        return null;
    }

    protected boolean checkSig(Signatory signatory, AssociationSession session, Assertion assertion)
    {
        _logger.debug("Calculating signature");

        String[] signed = StringUtils.split(assertion.getSigned(), ',');
        byte[] csig = signatory.sign(session.getMacKey(), assertion, signed);
        if (csig == null) return false;

        byte[] asig = assertion.getSig();

        if (_logger.isDebugEnabled())
        {
            _logger.debug(String.format("Calculated sig [%s], assertion sig [%s]",
                    new String(Hex.encodeHex(csig)), new String(Hex.encodeHex(asig))));
        }

        if (csig.length != asig.length) return false;
        for (int i = 0; i < csig.length; i++)
        {
            if (csig[i] != asig[i]) return false;
        }
        return true;
    }

    protected boolean checkAuth(String endpoint, Assertion assertion)
    {
        _logger.debug("Performing check_authentication");

        CheckAuthRequest request = new CheckAuthRequest(assertion);
        request.setAssocHandle(assertion.getAssocHandle());
        request.setSig(assertion.getSig());
        request.setSigned(assertion.getSigned());
        if (assertion.getInvalidateHandle() != null)
        {
            request.setInvalidateHandle(assertion.getInvalidateHandle());
        }
        String[] signed = StringUtils.split(assertion.getSigned(), ',');
        for (String key : signed)
        {
            if (!request.contains(key))
            {
                request.put(key, assertion.get(key));
            }
        }

        PostMethod method = new PostMethod(endpoint);
        for (String key : request.keySet())
        {
            method.addParameter(Constants.PARAMETER_PREFIX + key, request.get(key));
        }
        try
        {
            if (_httpClient.executeMethod(method) != HttpStatus.SC_OK)
            {
                throw new OpenIdException(String.format("Unexcept http status: %03d - %s",
                        method.getStatusCode(), method.getStatusText()));
            }

            String formString = Utils.readStringFromStream(method.getResponseBodyAsStream(),
                    method.getResponseContentLength(), method.getResponseCharSet());

            KeyValuePairs pairs = KeyValuePairs.parseFormString(formString);
            CheckAuthResponse response = new CheckAuthResponse(pairs);

            return response.isValid();
        }
        catch (IOException e)
        {
            throw new OpenIdException(String.format(
                    "Unable to check_authentication with openid server at %s", endpoint), e);
        }
        finally
        {
            method.releaseConnection();
        }
    }

    //

    public void setAssociationSessionManager(AssociationSessionManager value)
    {
        _associationSessionManager = value;
    }

    public AssociationSessionManager getAssociationSessionManager()
    {
        return _associationSessionManager;
    }

    private AssociationSessionManager _associationSessionManager;

    //

    public void setDiscoverers(List<Discoverer> value)
    {
        _discoverers = value;
    }

    public List<Discoverer> getDiscoverers()
    {
        return _discoverers;
    }

    private List<Discoverer> _discoverers;

    //

    public void setSignatories(List<Signatory> value)
    {
        _signatories = value;
    }

    public List<Signatory> getSignatories()
    {
        return _signatories;
    }

    private List<Signatory> _signatories;

    //

    public void setHttpClient(HttpClient value)
    {
        _httpClient = value;
    }

    public HttpClient getHttpClient()
    {
        return _httpClient;
    }

    private HttpClient _httpClient;

    private transient static final Log _logger = LogFactory.getLog(SignatureVerifier.class);
}
